#pragma once

#if defined(__linux__)

#include "IOMultiplexer.hpp"

#include <memory>
#include <unistd.h>
#include <sys/epoll.h>

namespace network {

    class Epoll : public IOMultiplexer {

    public:

        /**
          * @brief  创建一个epoll模型
          * @param  max_size 设置为每次wait返回的最大数量 默认为128
          */
        explicit Epoll(int max_size = 128);

        ~Epoll() override {
            close(epoll_fd_);
        };

        /**
          * @brief  获取第index个事件的引用
          * @note   如果index超出最大epoll_event数组长度，会抛出std::out_of_range异常
          * @param  index 索引
          * @retval 对应的epoll_event结构体的引用
          */
        struct epoll_event &operator[](int index);

        /**
          * @brief  设置epoll关注指定文件描述符的指定IO事件
          * @param  fd 要加入检测的文件描述符
          * @param  event_type 关注的IO事件
          * @retval 是否成功将事件添加到epoll模型上
          */
        bool add(int fd, EventType event_type) override;

        /**
          * @brief  修改epoll关注的事件类型
          * @note   None
          * @param  fd    要修改的文件描述符
          * @param  event 要关注的类型
          * @retval 是否修改成功
          */
        bool mod(int fd, EventType event) override;

        /**
          * @brief  将Epoll对象上指定的fd对应的epoll_event删除
          * @note   该函数在删除指定的fd后不会自动关闭该fd，请手动关闭
          * @param  要删除的fd
          * @retval 是否成功删除
          */
        bool del(int fd) override;

        /**
          * @brief  等待epoll文件描述符上的I/O事件
          * @note   epoll_wait返回小于0时会抛出runtime_error异常
          * @param  reactor 反应堆指针
          * @retval 就绪事件个数
          */
        int dispatch(Reactor *reactor) override;

    private:

        // epoll文件描述符
        int epoll_fd_;

        // epoll_event事件集合
        std::shared_ptr<struct epoll_event> events_;

        // epoll缓存事件
        struct epoll_event event_{};

        // 每次wait返回的epoll_event最大舒利昂
        int max_size_;

    };

    Epoll::Epoll(int max_size) : max_size_(max_size) {
        epoll_fd_ = epoll_create(1);
        events_ = std::shared_ptr<struct epoll_event>(new struct epoll_event[max_size_],
                                                 [](struct epoll_event *evs) { delete[] evs; });
    }

    struct epoll_event &Epoll::operator[](int index) {
        if (index > max_size_) {
            throw std::runtime_error("out of max epoll_event array length");
        }
        return events_.get()[index];
    }

    bool Epoll::add(int fd, EventType event_type) {
        uint32_t events = EPOLLET;
        if (hasEvent(event_type, EventType::Read)) {
            events |= EPOLLIN;
        }
        if (hasEvent(event_type, EventType::Write)) {
            events |= EPOLLOUT;
        }
        if (hasEvent(event_type, EventType::Close)) {
            events |= EPOLLIN;
        }
        event_.events = events;
        event_.data.fd = fd;
        int ret = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event_);
        return ret == 0;
    }

    bool Epoll::mod(int fd, EventType event) {
        uint32_t events = EPOLLET;
        if (hasEvent(event, EventType::Read) || hasEvent(event, EventType::Close)) {
            events |= EPOLLIN;
        }
        if (hasEvent(event, EventType::Write)) {
            events |= EPOLLOUT;
        }
        event_.events = events;
        event_.data.fd = fd;
        int ret = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &event_);
        return ret == 0;
    }

    bool Epoll::del(int fd) {
        int ret = epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr);
        return ret == 0;
    }

    int Epoll::dispatch(Reactor *reactor) {
        int ret = epoll_wait(epoll_fd_, events_.get(), max_size_, 0);
        if (ret < 0) {
            throw std::runtime_error("epoll_wait return < 0");
        }
        for (int i = 0; i < ret; ++i) {
            int fd = events_.get()[i].data.fd;
            if (events_.get()[i].events & EPOLLIN) {
                reactor->addEvent(fd, EventType::Read);
            } else if (events_.get()[i].events & EPOLLOUT) {
                reactor->addEvent(fd, EventType::Write);
            } else if (events_.get()[i].events & EPOLLRDHUP) {
                reactor->addEvent(fd, EventType::Close);
            }
        }
        return ret;
    }

}

#endif